use crate::{data::Segment, error::InvalidInstructionError};

use super::{
    Command, CommandType,
    asm::{AsmArithmeticOpt, AsmGenerator},
};

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Function {
    name: String,
    var_nums: u32,
}

impl TryFrom<&Command> for Function {
    type Error = InvalidInstructionError;
    /// parse function from string
    fn try_from(value: &Command) -> Result<Self, Self::Error> {
        if value.command_type != CommandType::Function {
            return Err(InvalidInstructionError::Command("not a function.".into()));
        }
        let name: String = value.arg1.clone();
        let var_nums = value.arg2.unwrap_or(0);
        Ok(Self { name, var_nums })
    }
}

impl Function {
    /// generate function asm code definition
    pub(crate) fn to_asm(&self, vm_file_name: &str) -> String {
        let mut asm_generator = AsmGenerator::new(vm_file_name);
        asm_generator.add_other(AsmGenerator::asm_label(self.label()));
        // initializes the local variables to 0
        for _ in 0..self.var_nums {
            // push 0 to stack
            asm_generator.push_segment(Segment::Constant, 0);
        }
        asm_generator.output()
    }
    /// get function label
    pub(crate) fn label(&self) -> &str {
        &self.name
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Call {
    pub(crate) name: String,
    arg_nums: u32,
    /// call return label counter
    counter: u32,
}

impl TryFrom<&Command> for Call {
    type Error = InvalidInstructionError;
    /// parse function from string
    fn try_from(value: &Command) -> Result<Self, Self::Error> {
        if value.command_type != CommandType::Call {
            return Err(InvalidInstructionError::Command("not a function.".into()));
        }
        let name: String = value.arg1.clone();
        let arg_nums = value.arg2.unwrap_or(0);
        Ok(Self {
            name,
            arg_nums,
            counter: 0,
        })
    }
}

impl Call {
    /// set the call counter
    pub(crate) fn set_counter(&mut self, counter: u32) {
        self.counter = counter;
    }
    /// generate function asm code definition
    ///
    /// # arguments
    ///
    /// * vm_file_name : the vm file name
    /// * counter : current call return label counter
    pub(crate) fn to_asm(&self, vm_file_name: &str) -> String {
        let return_label = format!("End${}${}", self.name, self.counter);
        let mut asm_generator = AsmGenerator::new(vm_file_name);
        // push return addr
        asm_generator.add_other(AsmGenerator::asm_addr(&return_label));
        asm_generator.add_other(AsmGenerator::asm_assignment(
            AsmGenerator::DREG,
            AsmGenerator::AREG,
        ));
        asm_generator.push_dreg_to_stack();
        // push LCL
        asm_generator.push_segment_base_address_to_stack(Segment::Local);
        // push ARG
        asm_generator.push_segment_base_address_to_stack(Segment::Argument);
        // push THIS
        asm_generator.push_segment_base_address_to_stack(Segment::This);
        // push THAT
        asm_generator.push_segment_base_address_to_stack(Segment::That);
        // ARG = SP-5-nArgs
        // AD = SP-5-nArgs
        asm_generator.addr_offset_and_store_in_a_and_d(
            AsmGenerator::SP,
            AsmArithmeticOpt::Sub,
            self.arg_nums + 5,
        );
        // ARG=D
        asm_generator.set_dreg_to_segment_base_address(Segment::Argument);
        // LCL = SP
        asm_generator.addr_offset_and_store_in_a_and_d(AsmGenerator::SP, AsmArithmeticOpt::Sub, 0);
        asm_generator.set_dreg_to_segment_base_address(Segment::Local);
        // goto the callee function
        asm_generator.jmp_to_label(&self.name);
        // return address
        asm_generator.add_other(AsmGenerator::asm_label(&return_label));
        asm_generator.output()
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Return;

impl TryFrom<&Command> for Return {
    type Error = InvalidInstructionError;
    /// parse function from string
    fn try_from(value: &Command) -> Result<Self, Self::Error> {
        if value.command_type != CommandType::Return {
            return Err(InvalidInstructionError::Command(
                "not a return instuction.".into(),
            ));
        }
        Ok(Self)
    }
}

impl Return {
    /// generate return asm code definition
    pub(crate) fn to_asm(&self) -> String {
        let mut asm_generator = AsmGenerator::new("");
        // frame(R15) = LCL
        //  - D = LCL
        asm_generator.addr_offset_and_store_in_a_and_d(
            &Segment::Local.to_asm_signal("", 0),
            AsmArithmeticOpt::Sub,
            0,
        );
        //  - R15 = D
        asm_generator.write_dreg_to_r15();
        // R14 = retAddr = *(frame - 5)
        //  - D = *(frame - 5)
        // 如果等待需要跳转才读取返回地址，在调用函数时无参数，执行完毕返回时，恢复ARG时会覆盖返回地址，造成无法正常返回!
        // 以上血的教训，整整排除了5个小时才发现，果然教材没有错，得按照教材顺序就不会出这样问题的！
        asm_generator.read_r15_value_to_dreg(5);
        //  - R14 = D
        asm_generator.write_dreg_to_r14();

        // *ARG = pop()
        asm_generator.pop_segment(Segment::Argument, 0);
        // SP = ARG + 1
        asm_generator.addr_offset_and_store_in_a_and_d(
            &Segment::Argument.to_asm_signal("", 0),
            AsmArithmeticOpt::Add,
            1,
        );
        asm_generator.set_dreg_to_base_address(AsmGenerator::SP);
        // THAT = *(frame - 1)
        asm_generator.set_r15_value_to_segment_base_addr(Segment::That, 1);
        // THIS = *(frame - 2)
        asm_generator.set_r15_value_to_segment_base_addr(Segment::This, 2);
        // ARG = *(frame - 3)
        asm_generator.set_r15_value_to_segment_base_addr(Segment::Argument, 3);
        // LCL = *(frame - 4)
        asm_generator.set_r15_value_to_segment_base_addr(Segment::Local, 4);
        // @R14 retAddr = *(frame - 5)
        asm_generator.add_other(AsmGenerator::asm_addr("R14"));
        // A=M
        asm_generator.add_other(AsmGenerator::asm_assignment(
            AsmGenerator::AREG,
            AsmGenerator::MREG,
        ));
        // goto ret addr
        asm_generator.add_other(AsmGenerator::asm_jmp().into());
        asm_generator.output()
    }
}
